			BITS		16

			SECTION		.text
buffer			EQU		512
image_size		EQU		512
; ================================================================
;	IBM-PC DISK BOOT LOADER FOR PROTECTED MODE EMBEDDED APPLICATIONS
;
;	This boot loader loads a file called "EMBEDDED.BIN" from
;	the same diskette into memory starting at physical address
;	zero. Before transferring control to the application, it
;	enables the A20 line to make memory above 1MB accessible
;	and disables the interrupt system. The application is
;	responsible for establishing its own protected mode 
;	interrupt service routines and for putting the processor
;	into protected mode.
; ================================================================

; ----------------------------------------------------------------
; Disk Area			Sectors
; -----------------------	----------------------------------
; Boot Loader (this code)	1
; FAT #1			sctrs_per_fat
; FAT #2			sctrs_per_fat
; Root Directory		(32 * dir_entries) / bytes_per_sctr
; Files Area			.....
; ----------------------------------------------------------------
	
		ORG	0
Boot_Loader:	JMP start	

oem_name:	DB	'EMBEDDED'	; Windows writes over these bytes!

; ----------------------------------------------------------------
; Bios Parameter Block (BPB)
; ----------------------------------------------------------------
bytes_per_sctr:	DW	512		; Bytes per sector
sctrs_per_clust:DB	1		; Sectors per cluster
rsvd_sectors:	DW	1		; Reserved sectors (the boot sector)
number_fats:	DB	2		; Number of FATs
dir_entries:	DW	224		; Number of root-directory entries
total_sctrs:	DW	2880		; Total sectors in logical volume
media_desc:	DB	0F0h		; Media descriptor byte
sctrs_per_fat:	DW	9		; Sectors per FAT

; ----------------------------------------------------------------
; Additional information (MS/DOS 3.0)
; ----------------------------------------------------------------
sctrs_per_track:DW	18		; Sectors per track
number_heads:	DW	2		; Number of heads
hidden_sctrs:	DW	0		; Number of hidden sectors

; ----------------------------------------------------------------
; Additional information (MS/DOS 4.0)
; ----------------------------------------------------------------
		DW	0		; MSW of 'hidden_sctrs' (above)
ttl_sctrs_vol:	DD	0		; total log sctrs in log volume
phys_drive:	DB	0		; physical drive number
		DB	0		; reserved
ext_boot_sig:	DB	29h		; extended boot signature
volume_id:	DD	0		; 32-bit binary volume ID
volume_label:	DB	'BOOT.LOADER'	; volume label
		DB	'????????'	; reserved

; ----------------------------------------------------------------
; Additional information written by COPYBOOT.EXE
; ----------------------------------------------------------------
fat_nibbles:	DW	0		; 4-bit Nibbles per FAT entry
bytes_per_clust:DW	0		; Bytes per cluster
dir_sector0:	DW	0		; log sctr # of 1st sctr in dir area
data_sector0:	DW	0		; log sctr # of 1st sctr in files area
dir_sectors:	DW	0		; # directory sectors
paras_per_clust:DW	0		; paragraphs per cluster
kb_needed:	DW	0		; total KB memory req'd by loader
end_of_chain:	DW	0		; FAT12 => 0FFFH or FAT16 => FFFFH

; ----------------------------------------------------------------
; Additional data NOT written by COPYBOOT.EXE
; ----------------------------------------------------------------
file2load:	DB	'EMBEDDEDBIN'	; Filename: "EMBEDDED.BIN"
	
; ----------------------------------------------------------------
start:		; Loader routine starts here
; ----------------------------------------------------------------
		STI			; Just in case
		CLD			; Needed by all REP's that follow
		XOR	AX,AX
		MOV	SS,AX		; The ROM BIOS only uses the lower
		MOV	SP,0400h	; half of the interrupt vector table

	; Relocate myself to the top of base memory

		PUSH	CS
		POP	DS
		CALL	next		; PUSH offset of 'next'
next:		POP	SI
		SUB	SI,next		; DS:SI = source address
		INT	12H		; AX = Memory Size in KB
		SUB	AX,[kb_needed+SI]	; Reserve room for loader
		SHL	AX,6		; AX = target segment
		MOV	ES,AX
		XOR	DI,DI		; ES:DI = target address
		MOV	CX,image_size
		REP	MOVSB
		PUSH	ES
		MOV	AX,continue
		PUSH	AX
		RETF

continue:	
	;; jc: 
	;; get video memory physical address using VBE calls
	mov ax,80h		; set overlaping... 
	mov es,ax		;   data and extra segments at 80h
	mov ds,ax		;   (at 0800h physical memory)
	mov di,ax		; VESA return buffer
	mov ax,4f01h		; VESA function 01 
	mov cx,105h		; for graphic mode 105 (1024x768@8bits)
 	int 10h			
	movd mm0, [es:di+40]	; save the graphic memory physical address
	;; /jc:
	
	MOV	AX,CS		; Now initialize DS and ES
	MOV	DS,AX		; so that our offsets are
	MOV	ES,AX		; meaningful.
	
		MOV	DI,[dir_sector0]; Load root directory
		MOV	BP,[dir_sectors]; into the buffer
		LEA	BX,[buffer]
		CALL	Read_Sectors	; (Modifies AX,BX,CX,DX,SI,DI,BP)
		CALL	Find_File	; (Modifies BX,CX,BP,SI)

		PUSH	DI		; DI = 1st cluster #
		MOV	DI,[rsvd_sectors]	; Load FAT into the buffer
		MOV	BP,[sctrs_per_fat]
		LEA	BX,[buffer]
		CALL	Read_Sectors	; (Modifies AX,BX,CX,DX,SI,DI,BP)
		POP	DI

		MOV	BX,0080H	; load address = 0080:0000
		MOV	ES,BX
BL1:		CALL	Read_Cluster	; (Modifies AX, CX, DX, BP, SI, ES)
		CALL	Next_Cluster	; (Modifies AX, CX, DX)
		MOV	AX,DI
		AND	AX,0FFF0h
		CMP	AX,[end_of_chain]
		JNE	BL1

	; save ending paragraph # for later

		PUSH	ES

	; Turn off floppy drive light 

		MOV	CX,100
WaitForDrive:	INT	08h		; Forced timer tick
		LOOP	WaitForDrive

	; Make memory above 1MB accessible

		CALL	Enable_A20
	
	;; jc: write prompt, a single '?'.
	;; Not a real prompt, but there are only 447 bytes for this bootloader...
	mov al,'?'
	xor bx,bx
	mov ah,0eh
	int 10h

	;; read a single key
 	xor ah,ah
	int 16h

	;; check typed key.
	;; A lower-case 'e' means text mode, any other key graphics mode
	cmp al,'e'
	je text
	
	;; jc:	enter graphic mode
	mov ax,4f02h ; VBE function 01
	mov bx,4105h ; bit 14=1, linear frame buffer, graphics mode mode 105h
 	int 10h
	jmp graph
	
	;; pass a 0 to  main(). The graphic memory adress was already setup in mm0
text:	xor eax,eax
	movd mm0,eax
	
graph:	 
	;; /jc:
	
	; The BIOS, its ISRs, and the interrupt vector
	; table are no longer needed. Disable interrupts
	; before writing over the interrupt vector table
	; and the BIOS data area just above it.

		CLI

	; Relocate image to 0000:0000
	; Can't use the stack anymore!

		POP	BP		; retrieve ending src paragraph #
		MOV	AX,0080H	; source segment
		XOR	BX,BX		; destination segment
		MOV	DX,[paras_per_clust]
		MOV	SP,[bytes_per_clust]
BL2:		MOV	CX,SP		; reload byte count
		MOV	DS,AX
		MOV	ES,BX
		XOR	SI,SI
		XOR	DI,DI
		REP	MOVSB
		ADD	AX,DX
		ADD	BX,DX
		CMP	AX,BP
		JB	BL2

	; transfer control to the application
	
	; jc: use EBX to pass args to main().
	; it is preserved across all calls until just before main() is called
	movd ebx, mm0 ; jc:
	
		DB	0EAh		; An direct intersegment
		DW	0,0		; jump to 0000:0000

Find_File:
; ----------------------------------------------------------------
; Parameters:	None
; Returns:	DI = Starting Cluster
; Modifies:	BX, CX, BP, SI
; ----------------------------------------------------------------
		MOV	BP,[dir_entries]; #entries to search
		XOR	BX,BX		; start w/entry #0
FF1:		CMP	BYTE [buffer+BX],0
		JE	FF3		; done if rest is unused
		TEST	BYTE [buffer+BX+11],00011000B
		JNZ	FF4		; skip if dir or vol label
		LEA	SI,[file2load]	; compare filename
		LEA	DI,[buffer+BX]
		MOV	CX,11
		REP	CMPSB
		JE	FF2
FF4:		ADD	BX,32		; BX -> next entry
		DEC	BP		; any more entries?
		JNZ	FF1
FF3:		HLT			; file not found
FF2:		MOV	DI,[buffer+BX+1Ah]
		RET

Read_Cluster:
; ----------------------------------------------------------------
; Parameters:	DI = current cluster # (Preserved)
;		ES = buffer segment
; Returns:	Nothing
; Modifies:	AX, CX, DX, BP, ES, SI
; ----------------------------------------------------------------
		PUSH	DI
		MOVZX	BP,BYTE [sctrs_per_clust]
		LEA	AX,[DI-2]	; 1st cluster in file space is #2
		MUL	BP
		ADD	AX,[data_sector0]
		MOV	DI,AX		; DI = 1st logical sector
		XOR	BX,BX		; load at offset 0 of segment
		CALL	Read_Sectors	; (Modifies AX,BX,CX,DX,SI,DI,BP)
		MOV	AX,ES
		ADD	AX,[paras_per_clust]
		MOV	ES,AX
		POP	DI
		RET

Next_Cluster:
; ----------------------------------------------------------------
; Parameters:	DI = current cluster #
; Returns:	DI = next cluster #
; Modifies:	AX, CX, DX
; ----------------------------------------------------------------
		MOV	AX,DI
		MUL	WORD [fat_nibbles]	; (DX will be zero)
		SHR	AX,1		; AX = rel offset into FAT entries
		PUSHF			; CF = 1: Left-Aligned FAT12 entry
		MOV	DI,AX
		POP	AX
		MOV	DI,[buffer+DI]
		CMP	WORD [fat_nibbles],4	; FAT12 or FAT16?
		JE	NC2		; If FAT16, we're done.
		SHR	AX,1		; Get CF
		JNC	NC1
		SHR	DI,4		; Left-aligned FAT12
NC1:		AND	DI,0FFFH
NC2:		RET

Read_Sectors:
; ----------------------------------------------------------------
; Parameters:	DI = 1st logical sector #
;		BP = sector count
;		ES:BX = load address
; Returns:	Nothing
; Modifies:	AX, BX, CX, DX, SI, DI, BP
; ----------------------------------------------------------------
		MOV	SI,3		; retry count
RS1:		MOV	AX,DI
		DIV	BYTE [sctrs_per_track]
		XOR	DX,DX		; DL = drive 0
		SHR	AL,1
		RCL	DH,1		; DH = head
		INC	AH		; disk sectors start at 1
		XCHG	AL,AH
		MOV	CX,AX		; CH = track, CL = sector
		MOV	AX,0201H	; Read 1 sector
		INT	13H
		JNC	RS3
		DEC	SI		; decrement retry count
		JNZ	RS2
		HLT			; halt after 3 attempts
RS2:		XOR	AX,AX		; reset disk...
		INT	13H
		JMP	RS1		; ...and try again
RS3:		MOV	SI,3		; reset retry count
		ADD	BX,[bytes_per_sctr]
		INC	DI		; next logical sector
		DEC	BP		; any more?
		JNZ	RS1
		RET

Wait8042:
; ----------------------------------------------------------------
; Wait for 8042 to complete previous command.
; ----------------------------------------------------------------
		XOR	CX,CX			; CX=65536 for timeout value
Wait8042Loop:	IN	AL,64h			; Read the status port, find
		TEST	AL,00000010b		;   out if the buffer is full
		LOOPNZ	Wait8042Loop		; Loop until buffer empty
		RET

Enable_A20:
; ----------------------------------------------------------------
; Enable the A20 line to make memory above 1MB accessible
; ----------------------------------------------------------------
		CALL	Wait8042		; Be sure 8042 is ready
		JNZ	EnableA20Failed
		MOV	AL,~00101110b	; Prepare to Write output port
		OUT	64h,AL
		CALL	Wait8042
		JNZ	EnableA20Failed
		MOV	AL,~00100000b	; Enable the A20 line
		OUT	60h,AL
		CALL	Wait8042
		JNZ	EnableA20Failed
		XOR	CX,CX			; Command started; give
EnableA20Wait:	LOOP	EnableA20Wait		;   it time to complete.
		RET
EnableA20Failed:HLT

		TIMES	510 - ($-$$) DB	0
		DW	0AA55h
